#define _CRT_SECURE_NO_WARNINGS 1


//		《 Linux_22_重定向_缓冲区 》



 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 读
 #include <unistd.h>
 从指定文件描述符 fd , 读到 buf 里， 读多少个字节
 ssize_t read(int fd, void *buf, size_t count);

      < stat >  //从指定文件描述符 fd, 获得一个 struct stat 的结构体
 int stat(const char *path, struct stat *buf);  //路径,      输出型参数
 int fstat(int fd, struct stat *buf);           //文件描述符,输出型参数 
 int lstat(const char *path, struct stat *buf); //路径,      输出型参数
 成功返回0， 失败返回 -1 错误码被设置 

 off_t    st_size;   /* total size, int bytes *///一共有多少字节


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

const char* filename = "log.txt";

int main()
{
    //不存在创建它 | 以W方式打开 | 打开后清空文件 ，默认权限 666
    //int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);

    struct stat st;         //OS 提供的内核数据结构

    int n = stat(filename, &st);
    if (n < 0) return 1;
    printf("file size: %lu\n", st.st_size); //无符号整数:file size: 12 

    int fd = open(filename, O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        return 2;
    }
    //const char *message = "hello Linux\n";
    //write(fd, message, strlen(message));

    printf("fd:%d\n", fd);  //fd:3 
    char* file_buffer = (char*)malloc(st.st_size + 1);
    n = read(fd, file_buffer, st.st_size);
    if (n > 0)
    {
        file_buffer[n] = '\0';  //准备把它当字符串来读，末尾 + '\0'
    }

    free(file_buffer);

    close(fd);
    return 0;
}

结果：
a@192 b]$ . / myfile
file size : 12
fd : 3
hello Linux


————————————————————————————————————————————————


————————————————————————————————————————————————


			《 文件描述符的分配规则 和 重定向 》


文件描述符的分配规则：查自己的文件描述表，分配最小的没有被使用的文件描述符fd.


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

const char* filename = "log.txt";

int main()
{
    //      close(0);       //关闭0 显示0
    //      close(1);       //关闭1 无显示
    //      close(2);       //关闭2 显示2.   关闭0 2 显示0

            //不存在创建它 | 以W方式打开 | 打开后清空文件 ，默认权限 666
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);

    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    printf("fd:%d\n", fd);
    fflush(stdout); //刷新文件缓冲区:关了没有数据，打开才有
    //原因:把底层文件描述符关了，就没法刷新到文件了, 
    //数据还在 stdout 还没刷新到文件或磁盘，打开的文件被关闭了，所以无显示

    close(fd);
    return 0;
}

重定向:
就是在内核中改变文件描述符表 特定下标的内容，和上层无关。
printf/fprintf -> stdout -> struct FILE -> (stdout->fileno) == 1 


————————————————————————————————————————————————


————————————————————————————————————————————————


			《 重定向：dup2() 》

        (把oldfd 拷贝给 newfd);
int dup2(int oldfd, int newfd);

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

const char* filename = "log.txt";

int main()
{
	//1.           (打开文件，没有就新建， 只写 ， 清空   ，权限);
	//int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);	// == 重定向	 '>'

	//2.           ( 文件名 ，没有就新建， 只写 ，  追加   ，权限);
	//int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);	// == 追加重定向 '>>'
	
	//3.         ( 文件路径 ，没有就新建， 只写 ，  追加   ，权限);
	int fd = open("/dev/pts/2", O_CREAT | O_WRONLY | O_APPEND, 0666);	// 向2号窗口

	if (fd < 0)
	{
		perror("open");
		return 1;
	}

	dup2(fd, 1);		//重定向：原本打印到 1(标准输出中)显示器的内容打印到 fd 文件中。 
	printf("hello world\n");
	fprintf(stdout, "hello world\n");

	return 0;    
}


————————————————————————————————————————————————

			《 缓冲区策略(用户级) 》
 
1.是什么: 缓冲区就是一段内存空间
2.为什么 : 给上层提供高效的IO体验，间接提高整体的效率
3.怎么办 :
	a.刷新策略 :
		1.	fflush(stdout);		//立即刷新,语言级 (无缓存)
	    1.1	int fsync(int fd);	//同步刷新,系统级 (无缓存)
	    2.	行刷新，一般是显示器 (效率与用户习惯兼顾)
		3.	全缓冲，缓冲区满了才刷新，一般是普通文件
		
	b.特殊情况:
		进程退出，系统会自动刷新。
		强制刷新 有点类似 1


————————————————————————————————————————————————


————————————————————————————————————————————————


			《 缓冲区 》
			< 缓冲区存在的意义 >

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>


const char* filename = "log.txt";

int main()
{
    //C 语言级
    printf("hello printf\n");             //stdout 对应的缓冲区 --> 用户
    fprintf(stdout, "hello fprintf\n");	//文件缓冲区，填满了才刷新。 

    //system call 系统级
    const char* msg = "hello write\n";
    write(1, msg, strlen(msg));			//直接写到内核里

    fork();	//？？？  / myfile > log.txt 时，普通文件刷新采用全缓冲。

    return 0;
}


[a@192 b]$ . / myfile
hello printf
hello fprintf
hello write

[a@192 b]$ . / myfile > log.txt
[a@192 b]$ cat log.txt
hello write
hello printf
hello fprintf
hello printf
hello fprintf

fork(); 的时候，文件缓冲区依旧没填满。 
程序结束时，父子进程各自进行一次刷新缓冲区。
所以语言级 的 printf 和 fprintf 被打印了两次。

缓冲区在哪呢？
[a@192 b]$ vim / usr / include / stdio.h 
typedef struct _IO_FILE __FILE; 

[a@192 b]$ vim / usr / include / libio.h
struct _IO_FILE 
{
	int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

 /		< 语言级的缓冲区 >
	/* The following pointers correspond to the C++ streambuf protocol. */
	/* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
	char* _IO_read_ptr;   /* Current read pointer */
	char* _IO_read_end;   /* End of get area. */
	... ...
 int _fileno;																// 内核级的文件描述符
	... ...
 
};

每打开一个文件，每个文件都有自己的文件缓冲区。


————————————————————————————————————————————————


————————————————————————————————————————————————




		< space > 
判断一个字符是否是空格:
#include <ctype.h>
int isspace(int c);	

		< open >
打开文件:
#include<sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);
int creat(const char* pathname, mode_t mode);


————————————————————————————————————————————————


————————————————————————————————————————————————


      《 模拟重定向 》

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 521
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do { p += strlen(p) -1; while(*p != '/') p--; } while(0) 

//#include <ctype.h>
//int isspace(int c);   //判断一个字符是否是空格。
//跳过所有空格
#define SkipSpace(cmd, pos) do{ while(1){ if(isspace(cmd[pos])) pos++; else break;}} while(0)

// "ls -a -l -n > myfile.txt"
#define None_Redir 0  //无重定向
#define In_Redir   1  //输入重定向
#define Out_Redir  2  //输出重定向
#define App_Redir  4  //追加重定向

int redir_type = None_Redir;//无重定向
char* filename = NULL;


//为了方便，直接定义测试
char cwd[SIZE * 2];
char* gArgv[NUM];
int lastcode = 0;

void Die()
{
    exit(1);
}

const char* GetHome()
{
    const char* home = getenv("HOME");
    if (home == NULL) return "/";
    return home;
}

//获取用户名
const char* GetUserName()
{
    const char* name = getenv("USER");
    if (name == NULL)
    {
        return "None";
    }
    return name;
}

//获取主机名
const char* GetHostName()
{
    const char* hostname = getenv("USER");
    if (hostname == NULL)
    {
        return "None";
    }
    return hostname;
}

//获取路径
const char* GetCwd()
{
    const char* cwd = getenv("PWD");
    if (cwd == NULL)
    {
        return "None";
    }
    return cwd;
}

void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char* username = GetUserName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();

    SkipPath(cwd);

    snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1);
    printf("%s", line);
    fflush(stdout);
}

int GetUsetCommand(char command[], size_t n)
{
    char* s = fgets(command, n, stdin);
    if (s == NULL)
        return -1;
    command[strlen(command) - 1] = ZERO;
    return strlen(command);
}


void SplitCommand(char command[], size_t n)
{
    (void)n;
    gArgv[0] = strtok(command, SEP);
    int index = 1;
    while ((gArgv[index++] = strtok(NULL, SEP)))
    {
        ;
    }
}

void ExecuteCommand()
{
    pid_t id = fork();
    if (id < 0)
        Die();
    else if (id == 0)
    {
        //重定向设置
        if (filename != NULL)
        {
            if (redir_type == In_Redir)
            {
                int fd = open(filename, O_RDONLY);
                dup2(fd, 0);    //标准输入，重定向到打开的文件
            }
            else if (redir_type == Out_Redir)
            {
                //( 以写的方式打开 | 文件不存在，创建 | 打开时清空文件 )
                int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                dup2(fd, 1);    //输出重定向
            }
            else if (redir_type == App_Redir)
            {
                //( 以写的方式打开 | 文件不存在，创建      |  追加   , 权限)
                int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                dup2(fd, 1);    //追加重定向
            }
            else
            {
            }
        }
        //最后执行是否重定向后的内容
        execvp(gArgv[0], gArgv);
        exit(errno);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
            lastcode = WEXITSTATUS(status);
            if (lastcode != 0)
                printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
        }
    }
}

void Cd()
{
    const char* path = gArgv[1];
    if (path == NULL)
        path = GetHome();
    chdir(path);

    char temp[SIZE * 2];
    getcwd(temp, sizeof(temp));
    snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
    putenv(cwd);
}

int CheckBuildin()
{
    int yes = 0;
    const char* enter_cmd = gArgv[0];
    if (strcmp(enter_cmd, "cd") == 0)
    {
        yes = 1;
        Cd();
    }
    else if (strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
    {
        yes = 1;
        printf("%d\n", lastcode);
        lastcode = 0;
    }

    return yes;
}

void CheckRedir(char cmd[])
{
    // "ls -a -l -n > myfile.txt"
    //以 '>' 分割左右两边
    int pos = 0;
    int end = strlen(cmd);
    while (pos < end)
    {
        if (cmd[pos] == '>')
        {
            if (cmd[pos + 1] == '>')
            {
                cmd[pos++] = 0;
                pos++;                  //跳过 '>>' 
                redir_type = App_Redir; //这是一个追加重定向
                SkipSpace(cmd, pos);    //跳过所有空格
                filename = cmd + pos;   //指向'>>'后(文件名位置)
            }
            else
            {
                cmd[pos++] = 0;
                redir_type = Out_Redir; //这是一个输出重定向
                SkipSpace(cmd, pos);    //跳过所有空格
                filename = cmd + pos;   //指向'>'后(文件名位置)
            }
        }
        else if (cmd[pos] == '<')
        {
            cmd[pos++] = 0;
            redir_type = In_Redir;          //这是一个输入重定向
            SkipSpace(cmd, pos);            //跳过所有空格
            filename = cmd + pos;           //指向 '<' 后(文件名位置)
        }
        else
        {
            pos++;  //不是我们要的分隔符，继续向后找

        }
    }
}

int main()
{
    int quit = 0;
    while (!quit)
    {
        //0. 重置
        int redir_type = None_Redir;
        filename = NULL;

        //1.我们需要自己输出一个命令行
        MakeCommandLineAndPrint();

        //2.获取用户命令字符串
        char usercommand[SIZE];
        int n = GetUserCommand(usercommand, sizeof(usercommand));
        if (n <= 0)
            return 1;
        //2.1 checkredir
        CheckRedir(usercommand);
        //2.2 debug
        printf("cmd: %s\n", usercommand);
        printf("redir: %d\n", redir_type);
        printf("filename: %s\n", filename);

        //3. 命令行分割
        SplitCommand(usercommand, sizeof(usercommand));

        //4.检测命令是否是 内建命令
        n = CheckBuildin();
        if (n)
            continue;
        // n. 
        ExecuteCommand();
    }
    return 0;
}


————————————————————————————————————————————————


————————————————————————————————————————————————

